package com.centerm.bluetooth.ibridge;

import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

import com.centerm.bluetooth.ibridge.BluetoothIBridgeAdapter.MyHandler;
import com.centerm.bluetooth.ibridge.constants.BondStatus;
import com.centerm.bluetooth.ibridge.constants.ConnectDirection;
import com.centerm.bluetooth.ibridge.constants.ConnectMode;
import com.centerm.bluetooth.ibridge.constants.ConnectStatus;
import com.centerm.bluetooth.ibridge.listener.ConnectionReceiver;
import com.centerm.bluetooth.ibridge.listener.DataReceiver;
import com.centerm.util.Logger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

final class BluetoothIBridgeConnManager implements ConnectionReceiver {
    private static final String TAG = "BluetoothIBridgeConnManager";

    // Member fields0
    private final BluetoothAdapter mAdapter;
    private final MyHandler mHandler;
    private ConnectList mConnectList;

    private ConnectionListener mListener;
    private Connections mConnections;
    private ConnectMode mConnectMode = ConnectMode.MODE_SMART;

    private List<ConnectMode> mSmartConnectModes = new ArrayList<>();
    private String mPincode = "0000";
    private boolean auth = true;
    private boolean autoPair = true;
    private ExecutorService threadPool;

    static final ConnectMode[] DefaultConnectModes = {ConnectMode.MODE_INSECURE_NOFIXED,
            ConnectMode.MODE_SECURE_FIXED, ConnectMode.MODE_SECURE_NOFIXED, ConnectMode.MODE_INSECURE_FIXED};


    /**
     * Constructor. Prepares a new BluetoothIBridgeAdapter session.
     */
    public BluetoothIBridgeConnManager(MyHandler handler) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = handler;
        mConnectList = new ConnectList();
        mConnections = new Connections(handler);
        threadPool = Executors.newFixedThreadPool(10);
    }

    /**
     * Start the chat service. Specifically start AcceptThread to begin a
     * session in listening (server) mode. Called by the Activity onResume()
     */
    public synchronized void listen() {
        log("listen");
        if (mListener == null) {
            mListener = new ConnectionListener(this, auth);
        }
        mListener.start();
    }

    /**
     * Stop all threads
     */
    public synchronized void destroy() {
        log("destroy");
        if (mConnections != null) {
            mConnections.disconnectAll();
        }
        if (mConnectList != null) {
            mConnectList.releaseAllConnects();
            mConnectList = null;
        }
        if (mListener != null) {
            mListener.stop();
            mListener = null;
        }
        if (threadPool != null) {
            threadPool.shutdown();
            threadPool = null;
        }
    }

    List<BluetoothIBridgeDevice> getCurrentConnectedDevice() {
        return mConnections.getCurrentConnectedDevice();
    }

    static void log(String content) {
        Logger.v(TAG, "+++++++++++++++" + "[" + Thread.currentThread().getName() + "] " + content);
    }

    /**
     * The class maintains a list of connected devices.Each element of list is a
     * device/socket pair.It supports add,remove connected devices and require
     * for a device(or some type) whether has connected.
     */
    private class ConnectList {

        private List<ConnectThread> mConnects = new ArrayList<>();
        private byte[] LOCK = new byte[0];

        public void add(ConnectThread conn) {
            synchronized (LOCK) {
                BluetoothIBridgeDevice device = conn.getDevice();
                for (final ConnectThread ds : mConnects) {
                    if (device.equals(ds.getDevice())) {//�жϵ�ǰ�������豸�Ƿ�������
                        log("Device already connect" + device);
                        return;
                    }
                }
                mConnects.add(conn);
            }
        }

        public void remove(ConnectThread conn) {
            synchronized (LOCK) {
                mConnects.remove(conn);
            }
        }

        public void cancel(BluetoothIBridgeDevice device) {
            synchronized (LOCK) {
                for (final ConnectThread ds : mConnects) {
                    if (ds != null && device.equals(ds.getDevice())) {
                        ds.cancel();
                        mConnects.remove(ds);
                        return;
                    }
                }
                log("Device lost connect" + device);
            }
        }

        public void releaseAllConnects() {
            synchronized (LOCK) {
                for (final ConnectThread ds : mConnects) {
                    if (ds != null) {
                        ds.cancel();
                    }
                }
                mConnects.clear();
            }
        }
    }

    /**
     * Start the ConnectThread to initiate a connection to a remote device.
     */
    synchronized void connect(final BluetoothIBridgeDevice device, final int timeout) {
        log("connect to: " + device);

        log("BluetoothIBridgeConnmanager" + "connect to: " + device);
        if (device == null) {
            log("device is null!");
            return;
        }

        if (device.isConnected()) {
            log("device already connected!");
            return;
        }

        device.setBondStatus();

        if (autoPair && device.getBondStatus().equals(BondStatus.STATE_BONDNONE)) {
            device.setBondStatus(BondStatus.STATE_BONDING);

        }

        device.setConnectStatus(ConnectStatus.STATUS_CONNECTTING);


        ConnectThread conn = new ConnectThread(device, timeout);
        conn.start();
        mConnectList.add(conn);


    }

    synchronized void disconnect(BluetoothIBridgeDevice device) {
        log("disconnect to: " + device);
        if (device != null && mConnections != null) {
            mConnections.disconnect(device);
        }
    }

    void write(BluetoothIBridgeDevice device, byte[] buffer, int length) {
        if (device != null && mConnections != null) {
            mConnections.write(device, buffer, length);
        }
    }


    /**
     * This thread runs while attempting to make an outgoing connection with a
     * device. It runs straight through; the connection either succeeds or
     * fails.
     */
    private class ConnectThread extends Thread {
        private BluetoothSocket mmSocket = null;
        private final BluetoothIBridgeDevice mmDevice;
        private final String name;
        private final int mmTimeout;
        private boolean cancelBond = false;
        private boolean retry = false;
        private boolean failed = true;
        private String exceptionMsg = null;
        private Future connFutureTask;
        private boolean isPaired = false;
        private boolean bonding = false;
        private int during = 0;
        private Handler handler = null;
        public final static int success = 2;
        public final static int fail = 3;


        public ConnectThread(BluetoothIBridgeDevice device, int timeout) {
            mmDevice = device;
            name = device.getDeviceName();
            mmTimeout = timeout;

        }

        @Override
        //�ж��豸�Ƿ����
        public boolean equals(Object o) {//��ԭ�в�ͬ
            if (o == null) {
                return false;
            }

            if (!(o instanceof ConnectThread)) {
                return false;
            }

            ConnectThread conn = (ConnectThread) o;
            return conn.mmDevice.equals(mmDevice);
        }

        //��ԭ�в�ͬ  �����豸
        public BluetoothIBridgeDevice getDevice() {
            return mmDevice;
        }

        //��ԭ�в�ͬ  ���ӳ�ʱ��ȡ������
        private void startTimer(final BluetoothIBridgeDevice device, final int time) {
            stopTimer();
            connFutureTask = threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    synchronized (this) {
                        try {
                            Thread.sleep(time * 1000);
                            if (device.getConnectStatus() != ConnectStatus.STATUS_CONNECTED) {
                                mConnectList.cancel(device);
                                log("connect timeout");
                            }
                        } catch (InterruptedException e) {
                            Logger.i(TAG, "connect timer interrupted");
                        }
                    }
                }
            });
        }

        //��ԭ�в�ͬ
        private void stopTimer() {
            if (connFutureTask != null && !connFutureTask.isCancelled() && !connFutureTask.isDone()) {
                connFutureTask.cancel(true);
                connFutureTask = null;
            }
        }

        private void sleepWait(int sec) {
            try {
                sleep(sec);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void run() {

            setName("ConnectThread" + name);
            if (mmDevice == null) {
                log("Device == null");
                return;
            }
            // Always cancel discovery because it will slow down a connection
            if (mAdapter.isDiscovering()) {
                mAdapter.cancelDiscovery();
            }

            mmDevice.setConnectStatus(ConnectStatus.STATUS_CONNECTTING);

            startTimer(mmDevice, mmTimeout);//���

            if (autoPair) {
                doBondProcess();
            }
            // Make a connection to the BluetoothSocket
            if (autoPair && !mmDevice.getBondStatus().equals(BondStatus.STATE_BONDED)) {
                exceptionMsg = "bond failed";
            } else {
                connectRfcommSocket();
            }

            stopTimer();

            if (failed) {
                try {
                    sleepWait(300);
                    if (mmSocket != null) {
                        mmSocket.close();
                        mmSocket = null;

                    }
                } catch (IOException e2) {
                    Logger.i(TAG, e2, "unable to close() " + name + " socket during connection failure");
                }
                connectionFailed(mmDevice, exceptionMsg);
                //

                return;
            }
            mmDevice.setConnectDirection(ConnectDirection.DIRECTION_FORWARD);
            mmDevice.setBondStatus();

            // Start the connected thread
            if (mmSocket != null) {
                mConnections.connected(mmSocket, mmDevice);
            }

            mConnectList.remove(this);

        }

        private void connectionFailed(BluetoothIBridgeDevice device, String exceptionMsg) {
            // Send a failure message back to the Activity
            if (device != null) {
                device.connected(false);
                device.setConnectStatus(ConnectStatus.STATUS_CONNECTFAILED);
            }
            Message msg = mHandler.obtainMessage(BluetoothIBridgeAdapter.MESSAGE_DEVICE_CONNECT_FAILED);
            msg.obj = device;

            Bundle bundle = new Bundle();
            bundle.putString(MyHandler.BUNDLE_EXCEPTION, exceptionMsg);
            msg.setData(bundle);

            mHandler.sendMessage(msg);

            mConnectList.cancel(device);
        }

        private void connectRfcommSocket() {
            int wait = 0;
            if (mConnectMode == ConnectMode.MODE_STRATEGY) {
                wait = 1;
                for (int i = 0; i < DefaultConnectModes.length; i++) {
                    ConnectMode mode = DefaultConnectModes[i];

                    connectRfcommSocket(mode, wait);
                    if (!failed) {
                        break;
                    }
                }
            } else if (mConnectMode == ConnectMode.MODE_SMART) {
                wait = 1;
                for (int i = 0; i < mSmartConnectModes.size(); i++) {
                    ConnectMode mode = mSmartConnectModes.get(i);
                    connectRfcommSocket(mode, wait);
                    if (!failed) {
                        if (i != 0) {
                            mSmartConnectModes.remove(i);
                            //	mSmartConnectModes.addFirst(mode);
                            mSmartConnectModes.add(0, mode);

                        }
                        break;
                    }
                }
            } else {
                connectRfcommSocket(mConnectMode, wait);
            }
        }

        private void connectRfcommSocket(ConnectMode mode, int wait) {

            int count = 2;
            mmSocket = mmDevice.createSocket(mode);
            if (wait > 0) {
                sleepWait(wait * 1000);
            }
            do {
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    if (mmSocket != null) {

                        mmSocket.connect();

                        failed = false;
                    } else {
                        Logger.i(TAG, "Socket == null");
                        failed = true;
                    }
                    retry = false;
                } catch (IOException e) {
                    failed = true;
                    exceptionMsg = e.getMessage();
                    retry = isServiceDiscoveryFailed(e.getMessage());
                    if (!retry || count == 1) {
                        Logger.e(TAG, e, "unable to connect() ", name);
                    }
                    if (retry && count != 1) {
                        sleepWait(300);
                    }
                }
            } while (retry && (--count > 0));


        }

        private void doBondProcess() {


            while (!cancelBond && !isPaired && during < mmTimeout * 5) {

                BluetoothDevice device = mAdapter.getRemoteDevice(mmDevice.getDeviceAddress());


                if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
                    isPaired = true;
                    bonding = false;
                    mmDevice.setBondStatus(BondStatus.STATE_BONDED);
                    Logger.i(TAG, "device bonded.");
                    break;
                } else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
                    mmDevice.setBondStatus(BondStatus.STATE_BONDING);

                    sleepWait(200);
                } else if (device.getBondState() == BluetoothDevice.BOND_NONE) {

                    if (!bonding) {

                        Logger.i(TAG, mmDevice + "start bond device");
                        mmDevice.createBond(device);
                        bonding = true;
                        mmDevice.setBondStatus(BondStatus.STATE_BONDING);
                    } else {
                        Logger.i(TAG, mmDevice + "bond failed");
                        mmDevice.setBondStatus(BondStatus.STATE_BONDFAILED);
                        return;
                    }
                    sleepWait(200);
                }
                during++;
            }
            if (cancelBond) {
                mmDevice.setBondStatus(BondStatus.STATE_BOND_CANCLED);
            } else if (!isPaired && during >= mmTimeout) {
                mmDevice.setBondStatus(BondStatus.STATE_BOND_OVERTIME);
            }
        }

        public void cancel() {
            try {
                cancelBondProcess();
                if (mmSocket != null) {
                    mmSocket.close();
                    mmSocket = null;
                }
            } catch (IOException e) {
                Logger.e(TAG, e, "close() of connect ", name, " socket failed");
            }
        }

        public void cancelBondProcess() {
            cancelBond = true;
        }

        private boolean isServiceDiscoveryFailed(String error) {
            if (null == error)
                return false;
            return error.equals("Service discovery failed");
        }
    }

    @SuppressLint("DefaultLocale")
    void onPairingRequested(BluetoothIBridgeDevice device, int type, int pairingKey) {
        String mPairingKey;
        switch (type) {
            case BluetoothIBridgeDevice.PAIRING_VARIANT_PIN:
                device.setPin(mPincode);
                break;
            case BluetoothIBridgeDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
            case BluetoothIBridgeDevice.PAIRING_VARIANT_CONSENT:
                device.setPairingConfirmation(true);
                break;
            case BluetoothIBridgeDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
                device.setPairingConfirmation(true);
                break;
            case BluetoothIBridgeDevice.PAIRING_VARIANT_DISPLAY_PIN:
                mPairingKey = String.format("%04d", pairingKey);
                device.setPin(mPairingKey);

                break;
            default:
        }
    }

    public void setConnectMode(ConnectMode mode) {
        mConnectMode = mode;
    }

    public void setPincode(String pincode) {
        mPincode = pincode;
    }

    public void setAutoBond(boolean auto) {
        autoPair = auto;
    }

    public void registerDataReceiver(DataReceiver receiver) {
        mConnections.registerDataReceiver(receiver);
    }

    public void unregisterDataReceiver(DataReceiver receiver) {
        mConnections.unregisterDataReceiver(receiver);
    }

    @Override
    public void onConnectionEstablished(BluetoothSocket socket) {
        BluetoothIBridgeAdapter.log("onConnectionEstablished,socket:" + socket);
        BluetoothIBridgeDevice device = BluetoothIBridgeDeviceFactory.getDefaultFactory().createDevice(
                socket.getRemoteDevice());
        if (device == null) {
            Logger.i(TAG, "Device == null");
            return;
        }
        device.setConnectDirection(ConnectDirection.DIRECTION_BACKWARD);
        device.setBondStatus();
        mConnections.connected(socket, device);
    }

    void setLinkKeyNeedAuthenticated(boolean authenticated) {
        if (mListener != null) {
            auth = authenticated;
            mListener.setLinkKeyNeedAuthenticated(authenticated);
        }
    }

    void setSmartConnectModes(ConnectMode[] modes) {
        mSmartConnectModes.clear();
        for (ConnectMode mode : modes) {
            mSmartConnectModes.add(mode);
        }
    }

    ConnectMode[] getSmartConnectModes() {
        ConnectMode[] modes = null;
        if (mSmartConnectModes.size() > 0)
            modes = (ConnectMode[]) mSmartConnectModes.toArray();
        return modes;
    }
}
